#include <initializer_list>
#include <iostream>
#include <type_traits>

#include <boost/beast/ssl.hpp>
#include <boost/beast.hpp>

#include "common/template.h"
#include "http_common.h"
#include "util/output.h"

// using ReadCallback = std::function<void (const beast::error_code &, size_t)>;


// static_assert(contains<std::string, Args...>::value, "不应包含回调函数");
// static_assert(contains<std::string, Args...>::value == false, "必须包含回调函数");
//         static_assert(!(contains<Http::StringResponse&, Args...>::value && \
//                       contains<Http::FileResponse&, Args...>::value), \
//                       "only need one response type" ); \
//         static_assert(contains<Http::StringResponse&, Args...>::value || \
//                       contains<Http::FileResponse&, Args...>::value, \
//                       "need a response" ); \
//

#define SYNC_HTTP_METHOD(method) \
    template <typename ...Args> \
    void method(const std::string &path, Args &&...args) \
    {   \
     make_request(beast::http::verb::method, path, std::forward<Args>(args)...); \
     request(std::get<sizeof...(args)-1>(std::tuple<Args &...>(args...))); \
    }

#define ASYNC_HTTP_METHOD(method) \
    template <typename ...Args> \
    void async_##method(const std::string &path, Args &&...args) \
    {   \
     make_request(beast::http::verb::method, path, std::forward<Args>(args)...); \
     async_request(std::get<sizeof...(args)-1>(std::tuple<Args &...>(args...))); \
    }

#define HTTP_METHOD(method) \
    SYNC_HTTP_METHOD(method) \
    ASYNC_HTTP_METHOD(method)

template <bool ssl>
class HttpClientImpl: public std::enable_shared_from_this<HttpClientImpl<ssl>>
{
public:
    HttpClientImpl(const std::string &host, uint16_t port);
    ~HttpClientImpl();

    HTTP_METHOD(get)
    HTTP_METHOD(post)

    void run()
    {
        _io_context.run();
    }

private:
    int _version;
    std::string _host;
    uint16_t _port;
    net::io_context _io_context;
    net::ip::tcp::resolver _resolver;
    Http::Context<ssl> _context;
    Http::StringRequest _req;
    bool _keep_alive;
    bool _connected;

    beast::flat_buffer _buffer;
    Http::UrlParam _url_param;
    Http::StringBody _body;
    Http::HeadParam _head_param;

private:
    void reconnect();
    void connect();
    void disconnect();
    template <typename Body>
    void request(http::response<Body> &);
    void response();
    template <typename Body>
    void async_request(Http::ResponseHandler<Body> hander);

    void setParam(const Http::UrlParam &url_param);
    void setParam(const Http::HeadParam &url_param);
    void setParam(const Http::StringBody &body);

    template <typename Body>
    void do_async_request(Http::ResponseHandler<Body> handler);

    template <typename ...Args>
    void make_request(beast::http::verb method, const std::string &path, Args &&...args)
    {
        std::tuple<Args &...> t(args...);    
        if constexpr(sizeof...(args) > 1) {    
            this->setParam(std::get<0>(t));    
        }    
        if constexpr(sizeof...(args) > 2) {    
            this->setParam(std::get<1>(t));    
        }    
        if constexpr(sizeof...(args) > 3) {    
            this->setParam(std::get<2>(t));    
        }    
        static_assert(sizeof...(args) <= 4, "too many args");   
        _req.target(path);    
        _req.method(method);    
    
    
        _req.set(http::field::host, _host);
        _req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        _req.version(_version);
        _req.keep_alive(_keep_alive);
        // head
        for (auto &[k, v] : _head_param) {
            _req.insert(k, v); 
        }   
    
        // url_param
        std::string param;
        for (auto &[k, v] : _url_param) {
            param += "&";
            param += k;
            param += "=";
            Http::convert(v);
            param += v;
        }   
        _req.target(std::string(_req.target()) + param);
        if (param.size() > 0) {
            param[0] = '?';
        }   
    
        if (_body.size()) {
            _req.body() = _body;
            _req.prepare_payload();
        }
        // COUT << _req.body();
    }

};

#include "http_client.ipp"

// http
using HttpClient = HttpClientImpl<false>;

// https
using HttpsClient = HttpClientImpl<true>;
